<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>几种跨域方式实现原理 | dmf的个人博客</title><meta name="author" content="dmf"><meta name="copyright" content="dmf"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="描述">
<meta property="og:type" content="article">
<meta property="og:title" content="几种跨域方式实现原理">
<meta property="og:url" content="https://gitee.com/dmf-c/blog/2021/06/11/js_file/CrossDomain/index.html">
<meta property="og:site_name" content="dmf的个人博客">
<meta property="og:description" content="描述">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://img1.baidu.com/it/u=3691791879,443411760&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889">
<meta property="article:published_time" content="2021-06-10T16:00:00.000Z">
<meta property="article:modified_time" content="2022-10-12T07:20:36.926Z">
<meta property="article:author" content="dmf">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://img1.baidu.com/it/u=3691791879,443411760&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889"><link rel="shortcut icon" href="/blog/img/favicon.png"><link rel="canonical" href="https://gitee.com/dmf-c/blog/2021/06/11/js_file/CrossDomain/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/blog/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/blog/',
  algolia: undefined,
  localSearch: undefined,
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: '几种跨域方式实现原理',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-10-12 15:20:36'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">27</div></a><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">0</div></a><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">6</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/blog/"><i class="fa-fw iconfont icon-shouyex"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw iconfont icon-zuixinwenzhang_huaban"></i><span> 找文章</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/blog/categories/"><i class="fa-fw iconfont icon-fenlei1"></i><span> 分类</span></a></li><li><a class="site-page child" href="/blog/tags/"><i class="fa-fw iconfont icon-biaoqian1"></i><span> 标签</span></a></li><li><a class="site-page child" href="/blog/archives/"><i class="fa-fw iconfont icon-shijianzhou"></i><span> 归档</span></a></li></ul></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://img1.baidu.com/it/u=3691791879,443411760&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=500&amp;h=889')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/blog/">dmf的个人博客</a></span><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/blog/"><i class="fa-fw iconfont icon-shouyex"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw iconfont icon-zuixinwenzhang_huaban"></i><span> 找文章</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/blog/categories/"><i class="fa-fw iconfont icon-fenlei1"></i><span> 分类</span></a></li><li><a class="site-page child" href="/blog/tags/"><i class="fa-fw iconfont icon-biaoqian1"></i><span> 标签</span></a></li><li><a class="site-page child" href="/blog/archives/"><i class="fa-fw iconfont icon-shijianzhou"></i><span> 归档</span></a></li></ul></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">几种跨域方式实现原理</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="fa-fw post-meta-icon far fa-calendar-alt"></i><span class="post-meta-label">发表于</span><time datetime="2021-06-10T16:00:00.000Z" title="发表于 2021-06-11 00:00:00">2021-06-11</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/Javascript/">Javascript</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="几种跨域方式实现原理"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h2 id="什么是跨域？"><a href="#什么是跨域？" class="headerlink" title="什么是跨域？"></a>什么是跨域？</h2><h3 id="什么是同源策略及其限制内容？"><a href="#什么是同源策略及其限制内容？" class="headerlink" title="什么是同源策略及其限制内容？"></a>什么是同源策略及其限制内容？</h3><p>同源策略是一种约定，它是浏览器最核心也最基本的安全功能，如果缺少了同源策略，浏览器很容易受到XSS、CSRF等攻击。所谓同源是指”协议+域名+端口”三者相同，即便两个不同的域名指向同一个ip地址，也非同源。</p>
<p><strong>同源策略限制内容有：</strong></p>
<p>Cookie、LocalStorage、IndexedDB 等存储性内容<br>DOM 节点<br>AJAX 请求发送后，结果被浏览器拦截了</p>
<p>但是有三个标签是允许跨域加载资源：</p>
<pre><code>&lt;img src=XXX&gt;
&lt;link href=XXX&gt;
&lt;script src=XXX&gt;
</code></pre>
<h3 id="常见跨域场景"><a href="#常见跨域场景" class="headerlink" title="常见跨域场景"></a>常见跨域场景</h3><p><strong>当协议、子域名、主域名、端口号中任意一个不相同时，都算作不同域。</strong>不同域之间相互请求资源，就算作“跨域”。常见跨域场景如下图所示：</p>
<img src="https://s1.ax1x.com/2022/10/10/xYWx58.png">

<p>注意：</p>
<p><strong>第一：如果是协议和端口造成的跨域问题“前台”是无能为力的。</strong></p>
<p><strong>第二：在跨域问题上，仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。</strong></p>
<p>这里或许有个疑问：请求跨域了，那么请求到底发出去没有？<br>跨域并不是请求发不出去，请求能发出去，服务端能收到请求并正常返回结果，只是结果被浏览器拦截了。明明通过表单的方式可以发起跨域请求，为什么 Ajax 就不会? 因为跨域是为了阻止用户读取到另一个域名下的内容，Ajax 可以获取响应，浏览器认为这不安全，所以拦截了响应。但是表单并不会获取新的内容，所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF，因为请求是发出去了。</p>
<h2 id="跨域解决方案"><a href="#跨域解决方案" class="headerlink" title="跨域解决方案"></a>跨域解决方案</h2><h3 id="jsonp"><a href="#jsonp" class="headerlink" title="jsonp"></a>jsonp</h3><p><strong>1) JSONP原理</strong></p>
<p><strong>利用 <code>&lt;script&gt;</code> 标签没有跨域限制的漏洞，网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。</strong></p>
<p><strong>2) JSONP和AJAX对比</strong></p>
<p>JSONP和AJAX相同，都是客户端向服务器端发送请求，从服务器端获取数据的方式。但AJAX属于同源策略，JSONP属于非同源策略（跨域请求）</p>
<p><strong>3) JSONP优缺点</strong></p>
<p>JSONP优点是简单兼容性好，可用于解决主流浏览器的跨域数据访问的问题。<strong>缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。</strong></p>
<p><strong>4) JSONP的实现流程</strong></p>
<ul>
<li>声明一个回调函数，其函数名(如show)当做参数值，要传递给跨域请求数据的服务器，函数形参为要获取目标数据(服务器返回的data)。</li>
<li>创建一个<code>&lt;script&gt;</code>标签，把那个跨域的API数据接口地址，赋值给script的src,还要在这个地址中向服务器传递该函数名（可以通过问号传参:?callback&#x3D;show）。</li>
<li>服务器接收到请求后，需要进行特殊的处理：把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如：传递进去的函数名是show，它准备好的数据是<code>show(&#39;我不爱你&#39;)</code>。</li>
<li>最后服务器把准备的数据通过HTTP协议返回给客户端，客户端再调用执行之前声明的回调函数（show），对返回的数据进行操作。</li>
</ul>
<p>在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的，这时候就需要自己封装一个 JSONP函数。</p>
<pre><code>// index.html
function jsonp(&#123; url, params, callback &#125;) &#123;
return new Promise((resolve, reject) =&gt; &#123;
    let script = document.createElement(&#39;script&#39;)
    window[callback] = function(data) &#123;
    resolve(data)
    document.body.removeChild(script)
    &#125;
    params = &#123; ...params, callback &#125; // wd=b&amp;callback=show
    let arrs = []
    for (let key in params) &#123;
    arrs.push(`$&#123;key&#125;=$&#123;params[key]&#125;`)
    &#125;
    script.src = `$&#123;url&#125;?$&#123;arrs.join(&#39;&amp;&#39;)&#125;`
    document.body.appendChild(script)
&#125;)
&#125;
jsonp(&#123;
url: &#39;http://localhost:3000/say&#39;,
params: &#123; wd: &#39;Iloveyou&#39; &#125;,
callback: &#39;show&#39;
&#125;).then(data =&gt; &#123;
console.log(data)
&#125;)
</code></pre>
<p>上面这段代码相当于向<code>http://localhost:3000/say?wd=Iloveyou&amp;callback=show</code>这个地址请求数据，然后后台返回<code>show(&#39;我不爱你&#39;)</code>，最后会运行show()这个函数，打印出’我不爱你’</p>
<pre><code>// server.js
let express = require(&#39;express&#39;)
let app = express()
app.get(&#39;/say&#39;, function(req, res) &#123;
let &#123; wd, callback &#125; = req.query
console.log(wd) // Iloveyou
console.log(callback) // show
res.end(`$&#123;callback&#125;(&#39;我不爱你&#39;)`)
&#125;)
app.listen(3000)
</code></pre>
<p><strong>5) jQuery的jsonp形式</strong></p>
<p>JSONP都是GET和异步请求的，不存在其他的请求方式和同步请求，且jQuery默认就会给JSONP的请求清除缓存。</p>
<pre><code>$.ajax(&#123;
url:&quot;http://crossdomain.com/jsonServerResponse&quot;,
dataType:&quot;jsonp&quot;,
type:&quot;get&quot;,//可以省略
jsonpCallback:&quot;show&quot;,//-&gt;自定义传递给服务器的函数名，而不是使用jQuery自动生成的，可省略
jsonp:&quot;callback&quot;,//-&gt;把传递函数名的那个形参callback，可省略
success:function (data)&#123;
console.log(data);&#125;
&#125;);
</code></pre>
<h3 id="cors"><a href="#cors" class="headerlink" title="cors"></a>cors</h3><p><strong>CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。</strong></p>
<p>浏览器会自动进行 CORS 通信，实现 CORS 通信的关键是后端。只要后端实现了 CORS，就实现了跨域。<br>服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源，如果设置通配符则表示所有网站都可以访问资源。<br>虽然设置 CORS 和前端没什么关系，但是通过这种方式解决跨域问题的话，会在发送请求时出现两种情况，分别为<strong>简单请求</strong>和<strong>复杂请求</strong>。</p>
<p><strong>1) 简单请求</strong></p>
<p>条件1：使用下列方法之一：</p>
<p>GET<br>HEAD<br>POST</p>
<p>条件2：Content-Type 的值仅限于下列三者之一：</p>
<p>text&#x2F;plain<br>multipart&#x2F;form-data<br>application&#x2F;x-www-form-urlencoded</p>
<p>请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器； XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。</p>
<p><strong>2) 复杂请求</strong></p>
<p>不符合以上条件的请求就肯定是复杂请求了。<br>复杂请求的CORS请求，会在正式通信之前，增加一次HTTP查询请求，称为”预检”请求,该请求是 option 方法的，通过该请求来知道服务端是否允许跨域请求。<br>我们用<code>PUT</code>向后台请求时，属于复杂请求，后台需做如下配置：</p>
<pre><code>// 允许哪个方法访问我
res.setHeader(&#39;Access-Control-Allow-Methods&#39;, &#39;PUT&#39;)
// 预检的存活时间
res.setHeader(&#39;Access-Control-Max-Age&#39;, 6)
// OPTIONS请求不做任何处理
if (req.method === &#39;OPTIONS&#39;) &#123;
res.end() 
&#125;
// 定义后台返回的内容
app.put(&#39;/getData&#39;, function(req, res) &#123;
console.log(req.headers)
res.end(&#39;我不爱你&#39;)
&#125;)
</code></pre>
<p>接下来我们看下一个完整复杂请求的例子，并且介绍下CORS请求相关的字段</p>
<pre><code>// index.html
let xhr = new XMLHttpRequest()
document.cookie = &#39;name=xiamen&#39; // cookie不能跨域
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open(&#39;PUT&#39;, &#39;http://localhost:4000/getData&#39;, true)
xhr.setRequestHeader(&#39;name&#39;, &#39;xiamen&#39;)
xhr.onreadystatechange = function() &#123;
if (xhr.readyState === 4) &#123;
    if ((xhr.status &gt;= 200 &amp;&amp; xhr.status &lt; 300) || xhr.status === 304) &#123;
    console.log(xhr.response)
    //得到响应头，后台需设置Access-Control-Expose-Headers
    console.log(xhr.getResponseHeader(&#39;name&#39;))
    &#125;
&#125;
&#125;
xhr.send()

//server1.js
let express = require(&#39;express&#39;);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

//server2.js
let express = require(&#39;express&#39;)
let app = express()
let whitList = [&#39;http://localhost:3000&#39;] //设置白名单
app.use(function(req, res, next) &#123;
let origin = req.headers.origin
if (whitList.includes(origin)) &#123;
    // 设置哪个源可以访问我
    res.setHeader(&#39;Access-Control-Allow-Origin&#39;, origin)
    // 允许携带哪个头访问我
    res.setHeader(&#39;Access-Control-Allow-Headers&#39;, &#39;name&#39;)
    // 允许哪个方法访问我
    res.setHeader(&#39;Access-Control-Allow-Methods&#39;, &#39;PUT&#39;)
    // 允许携带cookie
    res.setHeader(&#39;Access-Control-Allow-Credentials&#39;, true)
    // 预检的存活时间
    res.setHeader(&#39;Access-Control-Max-Age&#39;, 6)
    // 允许返回的头
    res.setHeader(&#39;Access-Control-Expose-Headers&#39;, &#39;name&#39;)
    if (req.method === &#39;OPTIONS&#39;) &#123;
    res.end() // OPTIONS请求不做任何处理
    &#125;
&#125;
next()
&#125;)
app.put(&#39;/getData&#39;, function(req, res) &#123;
console.log(req.headers)
res.setHeader(&#39;name&#39;, &#39;jw&#39;) //返回一个响应头，后台需设置
res.end(&#39;我不爱你&#39;)
&#125;)
app.get(&#39;/getData&#39;, function(req, res) &#123;
console.log(req.headers)
res.end(&#39;我不爱你&#39;)
&#125;)
app.use(express.static(__dirname))
app.listen(4000)
</code></pre>
<p>上述代码由<code>http://localhost:3000/index.html</code>向<code>http://localhost:4000/</code>跨域请求，正如我们上面所说的，后端是实现 CORS 通信的关键。</p>
<h3 id="postMessage"><a href="#postMessage" class="headerlink" title="postMessage"></a>postMessage</h3><p>postMessage是HTML5 XMLHttpRequest Level 2中的API，是可以跨域操作的window属性之一，可用于解决以下方面的问题：</p>
<ul>
<li>页面和其打开的新窗口的数据传递</li>
<li>多窗口之间消息传递</li>
<li>页面与嵌套的iframe消息传递</li>
<li>上面三个场景的跨域数据传递</li>
</ul>
<p><strong>postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信，可以实现跨文本档、多窗口、跨域消息传递。</strong></p>
<blockquote>
<p>otherWindow.postMessage(message, targetOrigin, [transfer]);</p>
</blockquote>
<ul>
<li>message: 将要发送到其他 window的数据。</li>
<li>targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件，其值可以是字符串”*”（表示无限制）或者一个URI。在发送消息的时候，如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值，那么消息就不会被发送；只有三者完全匹配，消息才会被发送。</li>
<li>transfer(可选)：是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方，而发送一方将不再保有所有权。</li>
</ul>
<p>接下来我们看个例子：<code>http://localhost:3000/a.html</code>页面向<code>http://localhost:4000/b.html</code>传递“我爱你”,然后后者传回”我不爱你”。</p>
<pre><code>// a.html
&lt;iframe src=&quot;http://localhost:4000/b.html&quot; frameborder=&quot;0&quot; id=&quot;frame&quot; onload=&quot;load()&quot;&gt;&lt;/iframe&gt; //等它加载完触发一个事件
//内嵌在http://localhost:3000/a.html
    &lt;script&gt;
    function load() &#123;
        let frame = document.getElementById(&#39;frame&#39;)
        frame.contentWindow.postMessage(&#39;我爱你&#39;, &#39;http://localhost:4000&#39;) //发送数据
        window.onmessage = function(e) &#123; //接受返回数据
        console.log(e.data) //我不爱你
        &#125;
    &#125;
    &lt;/script&gt;

// b.html
window.onmessage = function(e) &#123;
    console.log(e.data) //我爱你
    e.source.postMessage(&#39;我不爱你&#39;, e.origin)
&#125;
</code></pre>
<h3 id="websocket"><a href="#websocket" class="headerlink" title="websocket"></a>websocket</h3><p>Websocket是HTML5的一个持久化的协议，它实现了浏览器与服务器的全双工通信，同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议，都基于 TCP 协议。但是 <strong>WebSocket 是一种双向通信协议，在建立连接之后，WebSocket 的 server 与 client 都能主动向对方发送或接收数据</strong>。同时，WebSocket 在建立连接时需要借助 HTTP 协议，连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。</p>
<p>原生WebSocket API使用起来不太方便，使用<code>Socket.io</code>，它很好地封装了webSocket接口，提供了更简单、灵活的接口，也对不支持webSocket的浏览器提供了向下兼容。</p>
<p>我们先来看个例子：本地文件socket.html向<code>localhost:3000</code>发生数据和接受数据</p>
<pre><code>// socket.html
&lt;script&gt;
    let socket = new WebSocket(&#39;ws://localhost:3000&#39;);
    socket.onopen = function () &#123;
    socket.send(&#39;我爱你&#39;);//向服务器发送数据
    &#125;
    socket.onmessage = function (e) &#123;
    console.log(e.data);//接收服务器返回的数据
    &#125;
&lt;/script&gt;

// server.js
let express = require(&#39;express&#39;);
let app = express();
let WebSocket = require(&#39;ws&#39;);//记得安装ws
let wss = new WebSocket.Server(&#123;port:3000&#125;);
wss.on(&#39;connection&#39;,function(ws) &#123;
ws.on(&#39;message&#39;, function (data) &#123;
    console.log(data);
    ws.send(&#39;我不爱你&#39;)
&#125;);
&#125;)
</code></pre>
<h3 id="Node中间件代理-两次跨域"><a href="#Node中间件代理-两次跨域" class="headerlink" title="Node中间件代理(两次跨域)"></a>Node中间件代理(两次跨域)</h3><p>实现原理：<strong>同源策略是浏览器需要遵循的标准，而如果是服务器向服务器请求就无需遵循同源策略</strong>。 代理服务器，需要做以下几个步骤：</p>
<ul>
<li>接受客户端请求 。</li>
<li>将请求 转发给服务器。</li>
<li>拿到服务器 响应 数据。</li>
<li>将 响应 转发给客户端。</li>
</ul>
<p><a target="_blank" rel="noopener" href="https://imgse.com/i/xY4CBn"><img src="https://s1.ax1x.com/2022/10/10/xY4CBn.png" alt="xY4CBn.png"></a></p>
<p>本地文件index.html文件，通过代理服务器<code>http://localhost:3000</code>向目标服务器<code>http://localhost:4000</code>请求数据。</p>
<pre><code>// index.html(http://127.0.0.1:5500)
&lt;script src=&quot;https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js&quot;&gt;&lt;/script&gt;
    &lt;script&gt;
    $.ajax(&#123;
        url: &#39;http://localhost:3000&#39;,
        type: &#39;post&#39;,
        data: &#123; name: &#39;xiamen&#39;, password: &#39;123456&#39; &#125;,
        contentType: &#39;application/json;charset=utf-8&#39;,
        success: function(result) &#123;
        console.log(result) // &#123;&quot;title&quot;:&quot;fontend&quot;,&quot;password&quot;:&quot;123456&quot;&#125;
        &#125;,
        error: function(msg) &#123;
        console.log(msg)
        &#125;
    &#125;)
    &lt;/script&gt;

// server1.js 代理服务器(http://localhost:3000)
const http = require(&#39;http&#39;)
// 第一步：接受客户端请求
const server = http.createServer((request, response) =&gt; &#123;
// 代理服务器，直接和浏览器直接交互，需要设置CORS 的首部字段
response.writeHead(200, &#123;
    &#39;Access-Control-Allow-Origin&#39;: &#39;*&#39;,
    &#39;Access-Control-Allow-Methods&#39;: &#39;*&#39;,
    &#39;Access-Control-Allow-Headers&#39;: &#39;Content-Type&#39;
&#125;)
// 第二步：将请求转发给服务器
const proxyRequest = http
    .request(
    &#123;
        host: &#39;127.0.0.1&#39;,
        port: 4000,
        url: &#39;/&#39;,
        method: request.method,
        headers: request.headers
    &#125;,
    serverResponse =&gt; &#123;
        // 第三步：收到服务器的响应
        var body = &#39;&#39;
        serverResponse.on(&#39;data&#39;, chunk =&gt; &#123;
        body += chunk
        &#125;)
        serverResponse.on(&#39;end&#39;, () =&gt; &#123;
        console.log(&#39;The data is &#39; + body)
        // 第四步：将响应结果转发给浏览器
        response.end(body)
        &#125;)
    &#125;
    )
    .end()
&#125;)
server.listen(3000, () =&gt; &#123;
console.log(&#39;The proxyServer is running at http://localhost:3000&#39;)
&#125;)

// server2.js(http://localhost:4000)
const http = require(&#39;http&#39;)
const data = &#123; title: &#39;fontend&#39;, password: &#39;123456&#39; &#125;
const server = http.createServer((request, response) =&gt; &#123;
if (request.url === &#39;/&#39;) &#123;
    response.end(JSON.stringify(data))
&#125;
&#125;)
server.listen(4000, () =&gt; &#123;
console.log(&#39;The server is running at http://localhost:4000&#39;)
&#125;)
</code></pre>
<p>上述代码经过两次跨域，浏览器向代理服务器发送请求，也遵循同源策略，最后在index.html文件打印出<code>&#123;&quot;title&quot;:&quot;fontend&quot;,&quot;password&quot;:&quot;123456&quot;&#125;</code></p>
<h3 id="nginx反向代理"><a href="#nginx反向代理" class="headerlink" title="nginx反向代理"></a>nginx反向代理</h3><p>实现原理类似于Node中间件代理，需要你搭建一个中转nginx服务器，用于转发请求。</p>
<p>使用nginx反向代理实现跨域，是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题，支持所有浏览器，支持session，不需要修改任何代码，并且不会影响服务器性能。</p>
<p>实现思路：通过nginx配置一个代理服务器（域名与domain1相同，端口不同）做跳板机，反向代理访问domain2接口，并且可以顺便修改cookie中domain信息，方便当前域cookie写入，实现跨域登录。</p>
<p>先下载<a target="_blank" rel="noopener" href="http://nginx.org/en/download.html">nginx</a>，然后将nginx目录下的nginx.conf修改如下:</p>
<pre><code>// proxy服务器
server &#123;
    listen       81;
    server_name  www.domain1.com;
    location / &#123;
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时，此时无浏览器参与，故没有同源限制，下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时，可为*
        add_header Access-Control-Allow-Credentials true;
    &#125;
&#125;
</code></pre>
<p>最后通过命令行<code>nginx -s reload</code>启动nginx</p>
<pre><code>// index.html
var xhr = new XMLHttpRequest();
// 前端开关：浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open(&#39;get&#39;, &#39;http://www.domain1.com:81/?user=admin&#39;, true);
xhr.send();

// server.js
var http = require(&#39;http&#39;);
var server = http.createServer();
var qs = require(&#39;querystring&#39;);
server.on(&#39;request&#39;, function(req, res) &#123;
    var params = qs.parse(req.url.substring(2));
    // 向前台写cookie
    res.writeHead(200, &#123;
        &#39;Set-Cookie&#39;: &#39;l=a123456;Path=/;Domain=www.domain2.com;HttpOnly&#39;   // HttpOnly:脚本无法读取
    &#125;);
    res.write(JSON.stringify(params));
    res.end();
&#125;);
server.listen(&#39;8080&#39;);
console.log(&#39;Server is running at port 8080...&#39;);
</code></pre>
<h3 id="window-name-iframe"><a href="#window-name-iframe" class="headerlink" title="window.name + iframe"></a>window.name + iframe</h3><p>window.name属性的独特之处：name值在不同的页面（甚至不同域名）加载后依旧存在，并且可以支持非常长的 name 值（2MB）。</p>
<p>其中a.html和b.html是同域的，都是<code>http://localhost:3000</code>;而c.html是<code>http://localhost:4000</code></p>
<pre><code>// a.html(http://localhost:3000/b.html)
&lt;iframe src=&quot;http://localhost:4000/c.html&quot; frameborder=&quot;0&quot; onload=&quot;load()&quot; id=&quot;iframe&quot;&gt;&lt;/iframe&gt;
&lt;script&gt;
    let first = true
    // onload事件会触发2次，第1次加载跨域页，并留存数据于window.name
    function load() &#123;
    if(first)&#123;
    // 第1次onload(跨域页)成功后，切换到同域代理页面
        let iframe = document.getElementById(&#39;iframe&#39;);
        iframe.src = &#39;http://localhost:3000/b.html&#39;;
        first = false;
    &#125;else&#123;
    // 第2次onload(同域b.html页)成功后，读取同域window.name中数据
        console.log(iframe.contentWindow.name);
    &#125;
    &#125;
&lt;/script&gt;
</code></pre>
<p>b.html为中间代理页，与a.html同域，内容为空。</p>
<pre><code>// c.html(http://localhost:4000/c.html)
&lt;script&gt;
    window.name = &#39;我不爱你&#39;  
&lt;/script&gt;
</code></pre>
<p>总结：通过iframe的src属性由外域转向本地域，跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制，但同时它又是安全操作。</p>
<h3 id="location-hash-iframe"><a href="#location-hash-iframe" class="headerlink" title="location.hash + iframe"></a>location.hash + iframe</h3><p>实现原理： a.html欲与c.html跨域相互通信，通过中间页b.html来实现。 三个页面，不同域之间利用iframe的location.hash传值，相同域之间直接js访问来通信。</p>
<p>具体实现步骤：一开始a.html给c.html传一个hash值，然后c.html收到hash值后，再把hash值传递给b.html，最后b.html将结果放到a.html的hash值中。<br>同样的，a.html和b.html是同域的，都是<code>http://localhost:3000</code>;而c.html是<code>http://localhost:4000</code></p>
<pre><code>// a.html
&lt;iframe src=&quot;http://localhost:4000/c.html#iloveyou&quot;&gt;&lt;/iframe&gt;
&lt;script&gt;
    window.onhashchange = function () &#123; //检测hash的变化
    console.log(location.hash);
    &#125;
&lt;/script&gt;

// b.html
&lt;script&gt;
    window.parent.parent.location.hash = location.hash 
    //b.html将结果放到a.html的hash值中，b.html可通过parent.parent访问a.html页面
&lt;/script&gt;

// c.html
console.log(location.hash);
let iframe = document.createElement(&#39;iframe&#39;);
iframe.src = &#39;http://localhost:3000/b.html#idontloveyou&#39;;
document.body.appendChild(iframe);
</code></pre>
<h3 id="document-domain-iframe"><a href="#document-domain-iframe" class="headerlink" title="document.domain + iframe"></a>document.domain + iframe</h3><p><strong>该方式只能用于二级域名相同的情况下，比如 a.test.com 和 b.test.com 适用于该方式。</strong> 只需要给页面添加 <code>document.domain =&#39;test.com&#39;</code> 表示二级域名都相同就可以实现跨域。</p>
<p>实现原理：两个页面都通过js强制设置document.domain为基础主域，就实现了同域。</p>
<p>页面<code>a.zf1.cn:3000/a.html</code>获取页面<code>b.zf1.cn:3000/b.html</code>中a的值</p>
<pre><code>// a.html
&lt;body&gt;
helloa
&lt;iframe src=&quot;http://b.zf1.cn:3000/b.html&quot; frameborder=&quot;0&quot; onload=&quot;load()&quot; id=&quot;frame&quot;&gt;&lt;/iframe&gt;
&lt;script&gt;
    document.domain = &#39;zf1.cn&#39;
    function load() &#123;
    console.log(frame.contentWindow.a);
    &#125;
&lt;/script&gt;
&lt;/body&gt;

// b.html
&lt;body&gt;
hellob
&lt;script&gt;
    document.domain = &#39;zf1.cn&#39;
    var a = 100;
&lt;/script&gt;
&lt;/body&gt;
</code></pre>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul>
<li>CORS支持所有类型的HTTP请求，是跨域HTTP请求的根本解决方案</li>
<li>JSONP只支持GET请求，JSONP的优势在于支持老式浏览器，以及可以向不支持CORS的网站请求数据。</li>
<li>不管是Node中间件代理还是nginx反向代理，主要是通过同源策略对服务器不加限制。</li>
<li>日常工作中，用得比较多的跨域方案是cors和nginx反向代理</li>
</ul>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://gitee.com/dmf-c/blog">dmf</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://gitee.com/dmf-c/blog/2021/06/11/js_file/CrossDomain/">https://gitee.com/dmf-c/blog/2021/06/11/js_file/CrossDomain/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://gitee.com/dmf-c/blog" target="_blank">dmf的个人博客</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"></div><div class="post_share"><div class="social-share" data-image="https://img1.baidu.com/it/u=3691791879,443411760&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=500&amp;h=889" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/blog/2021/06/19/js_file/dataType/"><img class="prev-cover" src="https://img1.baidu.com/it/u=3691791879,443411760&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=500&amp;h=889" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">JavaScript的数据类型及其检测</div></div></a></div><div class="next-post pull-right"><a href="/blog/2021/05/23/css_file/ThreeColumns/"><img class="next-cover" src="https://img1.baidu.com/it/u=1828439677,2166285235&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=400&amp;h=300" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">实现三栏布局的几种方法</div></div></a></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" onerror="this.onerror=null;this.src='/blog/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">dmf</div><div class="author-info__description"></div></div><div class="card-info-data site-data is-center"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">27</div></a><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">0</div></a><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">6</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">业精于勤，荒于嬉。</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E8%B7%A8%E5%9F%9F%EF%BC%9F"><span class="toc-number">1.</span> <span class="toc-text">什么是跨域？</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E5%90%8C%E6%BA%90%E7%AD%96%E7%95%A5%E5%8F%8A%E5%85%B6%E9%99%90%E5%88%B6%E5%86%85%E5%AE%B9%EF%BC%9F"><span class="toc-number">1.1.</span> <span class="toc-text">什么是同源策略及其限制内容？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B8%B8%E8%A7%81%E8%B7%A8%E5%9F%9F%E5%9C%BA%E6%99%AF"><span class="toc-number">1.2.</span> <span class="toc-text">常见跨域场景</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%B7%A8%E5%9F%9F%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88"><span class="toc-number">2.</span> <span class="toc-text">跨域解决方案</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#jsonp"><span class="toc-number">2.1.</span> <span class="toc-text">jsonp</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#cors"><span class="toc-number">2.2.</span> <span class="toc-text">cors</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#postMessage"><span class="toc-number">2.3.</span> <span class="toc-text">postMessage</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#websocket"><span class="toc-number">2.4.</span> <span class="toc-text">websocket</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Node%E4%B8%AD%E9%97%B4%E4%BB%B6%E4%BB%A3%E7%90%86-%E4%B8%A4%E6%AC%A1%E8%B7%A8%E5%9F%9F"><span class="toc-number">2.5.</span> <span class="toc-text">Node中间件代理(两次跨域)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86"><span class="toc-number">2.6.</span> <span class="toc-text">nginx反向代理</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#window-name-iframe"><span class="toc-number">2.7.</span> <span class="toc-text">window.name + iframe</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#location-hash-iframe"><span class="toc-number">2.8.</span> <span class="toc-text">location.hash + iframe</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#document-domain-iframe"><span class="toc-number">2.9.</span> <span class="toc-text">document.domain + iframe</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-number">3.</span> <span class="toc-text">总结</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/blog/2023/01/08/vue_file/vuenote/" title="vue3入门笔记"><img src="https://img1.baidu.com/it/u=1828439677,2166285235&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=400&amp;h=300" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="vue3入门笔记"/></a><div class="content"><a class="title" href="/blog/2023/01/08/vue_file/vuenote/" title="vue3入门笔记">vue3入门笔记</a><time datetime="2023-01-07T16:00:00.000Z" title="发表于 2023-01-08 00:00:00">2023-01-08</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/02/20/vue_file/comm/" title="vue组件间通信六种方式"><img src="https://img1.baidu.com/it/u=1828439677,2166285235&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=400&amp;h=300" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="vue组件间通信六种方式"/></a><div class="content"><a class="title" href="/blog/2022/02/20/vue_file/comm/" title="vue组件间通信六种方式">vue组件间通信六种方式</a><time datetime="2022-02-19T16:00:00.000Z" title="发表于 2022-02-20 00:00:00">2022-02-20</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/02/05/wxEcharts/" title="微信小程序使用 echarts"><img src="https://img1.baidu.com/it/u=1828439677,2166285235&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=400&amp;h=300" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="微信小程序使用 echarts"/></a><div class="content"><a class="title" href="/blog/2022/02/05/wxEcharts/" title="微信小程序使用 echarts">微信小程序使用 echarts</a><time datetime="2022-02-04T16:00:00.000Z" title="发表于 2022-02-05 00:00:00">2022-02-05</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/01/30/vue_file/vue_router/" title="vue-router学习"><img src="https://img1.baidu.com/it/u=1828439677,2166285235&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=400&amp;h=300" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="vue-router学习"/></a><div class="content"><a class="title" href="/blog/2022/01/30/vue_file/vue_router/" title="vue-router学习">vue-router学习</a><time datetime="2022-01-29T16:00:00.000Z" title="发表于 2022-01-30 00:00:00">2022-01-30</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/01/16/vue_file/vuex/" title="Vuex使用方法"><img src="https://img1.baidu.com/it/u=1828439677,2166285235&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=400&amp;h=300" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="Vuex使用方法"/></a><div class="content"><a class="title" href="/blog/2022/01/16/vue_file/vuex/" title="Vuex使用方法">Vuex使用方法</a><time datetime="2022-01-15T16:00:00.000Z" title="发表于 2022-01-16 00:00:00">2022-01-16</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By dmf</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/blog/js/utils.js"></script><script src="/blog/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>